﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Red.GameData.Raw
{
    partial class DataCollection
    {
        /// <summary>
        /// Regenerate the mapping of the objects into the GUIDs - required after every file reload
        /// </summary>
        private void RegenerateObjectMap()
        {
            // discard current mappings
            AllObjects.Clear();
            _objectMap.Clear();

            // extract objects from the true sources - the files
            foreach (var file in AllFiles)
            {
                foreach (var obj in file.ObjectsInFile)
                {
                    AllObjects.Add(obj);
                    _objectMap[obj.ID] = obj;
                }
            }

            // add all of the unassigned objects from the special file to :)
            foreach (var obj in _unassignedObjects.ObjectsInFile)
            {
                AllObjects.Add(obj);
                _objectMap[obj.ID] = obj;
            }
        }

        /// <summary>
        /// Walk object graph marking reached objects
        /// </summary>
        private void WalkObjectGraph(DataObject obj, HashSet<DataObject> visited)
        {
            // null object, ignore it
            if (obj == null)
                return;

            // visit each object only once
            if (visited.Contains(obj))
                return;
            visited.Add(obj);

            // visit base object
            if (obj.BaseID != Guid.Empty)
            {
                var baseObj = Find(obj.BaseID);
                WalkObjectGraph(baseObj, visited);
            }

            // visit parent object
            if (obj.ParentID != Guid.Empty)
            {
                var baseObj = Find(obj.ParentID);
                WalkObjectGraph(baseObj, visited);
            }

            // visit objects referenced by value
            foreach (var prop in obj.Properties)
            {
                foreach (var value in prop.Values)
                {
                    Guid objectId = Guid.Empty;
                    if (Utils.ParseGuid(value.Value, ref objectId))
                    {
                        var otherObj = Find(objectId);
                        WalkObjectGraph(otherObj, visited);
                    }
                }
            }
        }

        /// <summary>
        /// Remove unreachable objects (GC)
        /// Starts from named objects and explores the tree, all unreachable object are removed
        /// NOTE: this operation will break the undo/redo
        /// </summary>
        public void CleanupUnreachableObjects(Raw.ReflectionData reflection)
        {
            // all objects are initially unreachable and no objects are visited
            var visited = new HashSet<DataObject>();
            int numSourceObject = AllObjects.Count;

            // start walking from the named objects
            int numNamedObjects = 0;
            foreach (var obj in AllObjects)
            {
                // resolve class
                var objectName = Utils.GetObjectName(reflection, obj);
                if (objectName != null && objectName != "")
                {
                    WalkObjectGraph(obj, visited);
                    numNamedObjects += 1;
                }
            }

            // remove objects that are NOT visited
            foreach (var obj in AllObjects)
            {
                if (!visited.Contains(obj))
                {
                    obj.File.MarkModified();
                    obj.File.ObjectsInFile.Remove(obj);
                }
            }

            // refresh object map
            RegenerateObjectMap();

            // stats
            System.Diagnostics.Debug.WriteLine("GC {0} -> {1} ({2} roots)",
                numSourceObject, AllObjects.Count, numNamedObjects);
        }
    }
}
